#include "preprocess/image_preprocessor.h"

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/opencv.hpp>

#include "common/file_helper.h"


bool resize_norm_chw_cpu_bgr(cv::Mat const& image, std::string const& name,
                             ImagePreProcessParameters const& parameters,
                             float* out, std::string& err) noexcept {
  auto channels{image.channels()};

  if (3 != channels && 1 != channels) {
    err = "only support 1&3 channel image";
    return false;
  }

  auto& crop_area{parameters.crop_area};
  // check parameter
  auto resize_h{image.rows * parameters.resize_scale};
  auto resize_w{image.cols * parameters.resize_scale};

  if (crop_area.x > resize_w || crop_area.width > resize_w ||
      crop_area.y > resize_h || crop_area.height > resize_h) {
    err = "crop area out of resize image";
    return false;
  }

  // resize image
  cv::Mat resize_img;
  auto& resize_scale{parameters.resize_scale};
  cv::resize(image, resize_img, cv::Size2i(0, 0), resize_scale, resize_scale);

  // crop image
  auto crop_img{resize_img(
      cv::Rect2i(crop_area.x, crop_area.y, crop_area.width, crop_area.height))};
  // cv::imwrite(name + ".jpg", crop_img);

  // norm and reshape

  auto stride{crop_img.rows * crop_img.cols};

  // auto img_test{cv::imread("../data/1_img.png")};

  auto const buffer_size{stride * channels};

  auto scale{(float)1};

  for (unsigned r = 0; r < crop_img.rows; ++r) {
    for (unsigned c = 0; c < crop_img.cols; ++c) {
      // 3 channels
      if (3 == channels) {
        cv::Vec3b bgr{crop_img.at<cv::Vec3b>(r, c)};

        out[0 * stride + r * crop_img.cols + c] =
            (float(bgr[0]) * scale - parameters.norm_mean.at(0)) /
            parameters.norm_std.at(0);

        out[1 * stride + r * crop_img.cols + c] =
            (float(bgr[1]) * scale - parameters.norm_mean.at(1)) /
            parameters.norm_std.at(1);

        out[2 * stride + r * crop_img.cols + c] =
            (float(bgr[2]) * scale - parameters.norm_mean.at(2)) /
            parameters.norm_std.at(2);
      }
      // 1 channel
      if (1 == channels) {
        auto gray{image.at<uchar>(r, c)};
        out[r * crop_img.cols + c] =
            ((float)gray * scale - parameters.norm_mean.at(0)) /
            parameters.norm_std.at(0);
      }
    }
  }
  return true;
}

bool resize_norm_chw_cpu(cv::Mat const& image,
                         ImagePreProcessParameters const& parameters,
                         void* const dev_addr, std::string& err) noexcept {
  auto channels{image.channels()};

  if (3 != channels && 1 != channels) {
    err = "only support 1&3 channel image";
    return false;
  }

  auto& crop_area{parameters.crop_area};
  // check parameter
  auto resize_h{image.rows * parameters.resize_scale};
  auto resize_w{image.cols * parameters.resize_scale};

  if (crop_area.x > resize_w || crop_area.width > resize_w ||
      crop_area.y > resize_h || crop_area.height > resize_h) {
    err = "crop area out of resize image";
    return false;
  }

  // resize image
  cv::Mat resize_img;
  auto& resize_scale{parameters.resize_scale};
  cv::resize(image, resize_img, cv::Size2i(0, 0), resize_scale, resize_scale);

  // crop image
  auto crop_img{resize_img(
      cv::Rect2i(crop_area.x, crop_area.y, crop_area.width, crop_area.height))};
  // cv::imwrite("test.jpg", crop_img);

  // norm and reshape

  auto stride{crop_img.rows * crop_img.cols};

  // auto img_test{cv::imread("../data/1_img.png")};

  auto const buffer_size{stride * channels};
  std::vector<float> img_buffer;
  img_buffer.resize(buffer_size);

  auto scale{(float)1 / 255};

  for (unsigned r = 0; r < crop_img.rows; ++r) {
    for (unsigned c = 0; c < crop_img.cols; ++c) {
      // 3 channels
      if (3 == channels) {
        cv::Vec3b bgr{crop_img.at<cv::Vec3b>(r, c)};

        img_buffer[0 * stride + r * crop_img.cols + c] =
            (float(bgr[2]) * scale - parameters.norm_mean.at(0)) /
            parameters.norm_std.at(0);

        img_buffer[1 * stride + r * crop_img.cols + c] =
            (float(bgr[1]) * scale - parameters.norm_mean.at(1)) /
            parameters.norm_std.at(1);

        img_buffer[2 * stride + r * crop_img.cols + c] =
            (float(bgr[0]) * scale - parameters.norm_mean.at(2)) /
            parameters.norm_std.at(2);
      }
      // 1 channel
      if (1 == channels) {
        auto gray{image.at<uchar>(r, c)};
        img_buffer[r * crop_img.cols + c] =
            ((float)gray * scale - parameters.norm_mean.at(0)) /
            parameters.norm_std.at(0);
      }
    }
  }

  // // 对数
  // std::vector<float> oriData;
  // read_bin_file("../data/2_img.bin", oriData);
  // double sum = 0;
  // for (size_t i = 0; i < oriData.size(); i++) {
  //   sum += abs(img_buffer[i] - oriData[i]);
  //   // std::cout << img_buffer[i] << "   <----------->    " << oriData[i]
  //   //           << std::endl;
  // }
  // std::cout << "----------> img pre bias: " << sum << std::endl;
  // /////////---end---///////////

// todo
    return true;

}

ResizeNormCHWNPP::ResizeNormCHWNPP() noexcept {
  gpu_img_buf_size_ = 0;
  gpu_resize_img_buf_size_ = 0;
  gpu_crop_img_buf_size_ = 0;
  gpu_data_buf_size_ = 0;
}

ResizeNormCHWNPP::~ResizeNormCHWNPP() {}

bool ResizeNormCHWNPP::operator()(cv::Mat const& image,
                                  ImagePreProcessParameters const& parameters,
                                  void* const dev_addr,
                                  std::string& err) noexcept {
  auto channels{image.channels()};
  if (3 != channels && 1 != channels) {
    err = "only support 1&3 channel image";
    return false;
  }

  auto& crop_area{parameters.crop_area};
  // check parameter
  auto resize_h{static_cast<int>(image.rows * parameters.resize_scale)};
  auto resize_w{static_cast<int>(image.cols * parameters.resize_scale)};

  if (crop_area.x > resize_w || crop_area.width > resize_w ||
      crop_area.y > resize_h || crop_area.height > resize_h) {
    err = "crop area out of resize image";
    return false;
  }

  if (parameters.norm_mean.size() != channels ||
      parameters.norm_std.size() != channels) {
    err = "norm parameter error, norm size is not equal channels";
    return false;
  }

  // todo

  return true;
}